module Aggregator
  
  module FeedTagSearchTemplates
    TECHNORATI = 'http://api.technorati.com/tag?key={apikey}&tag={tag}&format=rss&limit=100'
    GOOGLEBLOG = 'http://blogsearch.google.com/blogsearch_feeds?hl=en&q=%22{tag}%22&ie=utf-8&num=10&output=atom'
    GOOGLENEWS = 'http://news.google.com/news?hl=en&ned=us&q=%22{tag}%22&btnG=Search+News&output=atom'
    FLICKR = 'http://api.flickr.com/services/feeds/photos_public.gne?tags={tag}&lang=en-us&format=rss_200'
    WORDPRESS = 'http://wordpress.com/tag/{tag}/feed/'
    CITEULIKE = 'http://www.citeulike.org/rss/tag/{tag}'
    DELICIOUS = 'http://del.icio.us/rss/tag/{tag}'
  end
  
  module FeedModel
    
    def self.included(base)
      base.class_eval do
        validates_presence_of :uri, :service
        validates_uniqueness_of :uri
        
        has_many :entries
        has_many :aggregation_feeds
        has_many :aggregations, :through => :aggregation_feeds
        
        belongs_to :service
        
        attr_accessor :problem_feeds
        attr_accessor :added_feeds
        attr_accessor :already_feeds
        attr_accessor :tags
        
        attr_accessor :technorati
        attr_accessor :google_news
        attr_accessor :google_blog
        attr_accessor :delicious
        attr_accessor :flickr
        attr_accessor :wordpress
        
        attr_accessor :tag_splitter            
      end
      base.extend(ClassMethods)
      
      tag_splitter = ' '
              
    end
    
    module ClassMethods
      
      ##########################################################
      # any give feed uri should only exist once in the system
      def find_or_create(uri, title, login, password, service_id)
        Feed.find_by_uri(uri) ||
        Feed.new(:uri => uri,
                 :harvest_interval => '01:00:00',
        :last_harvested_at => Time.at(0),
        :title => title,
        :login => login,
        :password => password,
        :service_id => service_id)
      end
      
      ##########################################################
      # any give feed uri should only exist once in the system
      def setup_new(uri, title, login, password, service_id)
        Feed.new(:uri => uri,
                 :harvest_interval => '01:00:00',
        :last_harvested_at => Time.at(0),
        :title => title,
        :login => login,
        :password => password,
        :service_id => service_id)
      end
      
    end
    
    ##########################################################
    # search feeds
    def self.search(terms, options = {})
        sql = "SELECT feeds.* FROM feeds "
        sql << "WHERE "
        sql << make_and_sql_where("feeds.title", terms, "%")
        sql << " OR "
        sql << make_and_sql_where("feeds.uri", terms, "%")
        sql << " WHERE "
        sql << terms.collect {|tag| sanitize_sql( ["tags.name LIKE ?", tag])}.join(" OR ")
        add_limit!(sql, options)
        find_by_sql(sql)
    end 
    
    ##########################################################
    # creates a feed for a tag based on a template
    def create_tag_feed(uri_template, tags, service, aggregation)
        tags.each do |tag|
            title = tag + ' on ' + uri_template[:title]
            tag = CGI::escape(tag)
            uri = uri_template[:uri]

            if uri_template[:apikey]
                apikey = uri_template[:apikey]
                uri = uri.sub("{apikey}", apikey)
            end

            service_id = service.id
            
            if uri_template[:service_id]                
                service_id = uri_template[:service_id]
            end
            
            uri = uri.sub("{tag}", tag)
            feed = Feed.find_or_create(uri, title, '', '', service_id)
            feed.user = nil
            add_feed_to_aggregation(aggregation, feed)
        end
    end
    
    ##########################################################
    # this method will examine the service and parameters then create
    # and add the appropriate feeds
    def create_feeds(user, local_params, service, aggregation)

        uri_templates = Array.new
             
        if service.id == Aggregator::ServiceConstants::TAG
        
            self.technorati = local_params[:technorati]
            self.google_news = local_params[:google_news]
            self.google_blog = local_params[:google_blog]
            self.delicious = local_params[:delicious]
            self.flickr = local_params[:flickr]
            self.wordpress = local_params[:wordpress]
        
            uri_templates.push(:title => 'Technorati', :uri => Aggregator::FeedTagSearchTemplates::TECHNORATI, :apikey => Technorati::APIKey, :service_id => Aggregator::ServiceConstants::TECHNORATI) if self.technorati == '1'
            uri_templates.push(:title => 'Google News', :uri => Aggregator::FeedTagSearchTemplates::GOOGLENEWS, :service_id => Aggregator::ServiceConstants::GOOGLENEWS) if self.google_news == '1'
            uri_templates.push(:title => 'Google Blog Search', :uri => Aggregator::FeedTagSearchTemplates::GOOGLEBLOG, :service_id => Aggregator::ServiceConstants::GOOGLEBLOG) if self.google_blog == '1'
            uri_templates.push(:title => 'Delicious', :uri => Aggregator::FeedTagSearchTemplates::DELICIOUS, :service_id => Aggregator::ServiceConstants::DELICIOUS) if self.delicious == '1'
            uri_templates.push(:title => 'Flickr', :uri => Aggregator::FeedTagSearchTemplates::FLICKR, :service_id => Aggregator::ServiceConstants::FLICKR) if self.flickr == '1'
            uri_templates.push(:title => 'Wordpress', :uri => Aggregator::FeedTagSearchTemplates::WORDPRESS, :service_id => Aggregator::ServiceConstants::WORDPRESS) if self.wordpress == '1'
        elsif service.id == Aggregator::ServiceConstants::FLICKR
            uri_templates.push(:title => 'Flickr', :uri => Aggregator::FeedTagSearchTemplates::FLICKR)
        elsif service.id == Aggregator::ServiceConstants::WORDPRESS
            uri_templates.push(:title => 'Wordpress', :uri => Aggregator::FeedTagSearchTemplates::WORDPRESS)
        elsif service.id == Aggregator::ServiceConstants::CITEULIKE
            uri_templates.push(:title => 'CiteULike', :uri => Aggregator::FeedTagSearchTemplates::CITEULIKE)
        elsif service.id == Aggregator::ServiceConstants::GOOGLENEWS
            uri_templates.push(:title => 'Google News', :uri => Aggregator::FeedTagSearchTemplates::GOOGLENEWS)
        elsif service.id == Aggregator::ServiceConstants::TECHNORATI
            uri_templates.push(:title => 'Technorati', :uri => Aggregator::FeedTagSearchTemplates::TECHNORATI)
        elsif service.id == Aggregator::ServiceConstants::GOOGLEBLOG
            uri_templates.push(:title => 'Google Blog Search', :uri => Aggregator::FeedTagSearchTemplates::GOOGLEBLOG)
        elsif service.id == Aggregator::ServiceConstants::DELICIOUS
            uri_templates.push(:title => 'Delicious', :uri => Aggregator::FeedTagSearchTemplates::DELICIOUS)
        end
        
        self.tags = local_params[:tags]
        tags = local_params[:tags].nil? ? [] : local_params[:tags].split(self.tag_splitter)
        self.login = login = local_params[:login] || ''
        self.password = password = local_params[:password] || ''
        self.uri = uri = local_params[:uri] || ''
        
        if service.id != Aggregator::ServiceConstants::RSS
        
            if tags.length > 0
                uri_templates.each do |uri_template|
                    create_tag_feed(uri_template, tags, service, aggregation)
                end
            end
        
            if login.length > 0
                create_username_password_feed(user, login, password, service, aggregation)
            end

        end        
        
        if uri.length > 0
            create_uri_feed(uri, aggregation, service)
        end
        
    end
    
    ##########################################################
    # adds a feed to an aggregation and saves the feed
    def add_feed_to_aggregation(aggregation, feed)
        
        if aggregation.feeds.include?(feed)
            self.already_feeds.push(feed)
        else
            if feed.save # have to save here so that feed has an id.  Without id can't associate with aggregation
                feed.aggregations << aggregation
                self.added_feeds.push(feed )
            end
        end

        feed.save
    end
    
    ##########################################################
    # Get all tags applied to entries published to a feed
    #
    def get_tags(options = {})
      sql = "SELECT t.id, t.name, COUNT(t.id) AS count "
      sql << "FROM tags t "
      sql << "INNER JOIN entries_tags ON t.id = entries_tags.tag_id "
      sql << "INNER JOIN entries ON entries_tags.entry_id = entries.id "
      sql << "WHERE entries.feed_id = ? "
      sql << "GROUP BY t.id, t.name "
      sql << "ORDER BY count DESC"
      add_limit!(sql, options)
      
      result = Tag.find_by_sql([sql,self.id])
      count = result.inject({}) { |hsh, row| hsh[row['name']] = row['count'].to_i; hsh } unless options[:raw]
      count || result
    end
    
    ##########################################################
    # Finds entries from feeds that are tagged with the given tags
    #
    def get_entries_tagged(tags, options = {})
      sql = "SELECT DISTINCT ON(entries.published_at, entries.id) entries.*, oage(entries.published_at) as age, feeds.service_id "
      sql << "FROM entries "
      sql << "INNER JOIN entries_tags ON entries.id = entries_tags.entry_id "
      sql << "INNER JOIN tags ON entries_tags.tag_id = tags.id "
      sql << "INNER JOIN feeds ON entries.feed_id = feeds.id "
      sql << "WHERE entries.feed_id = ? AND "
      sql << make_and_sql_where("tags.name", tags)
      sql << "ORDER BY entries.published_at DESC, entries.id "
      add_limit!(sql, options)
      Entry.find_by_sql([sql, self.id])
    end
    
    #############################################################
    # Gets feeds that contain shared entries tagged specified way
    #
    # Called by:
    #   feed_controller.feeds_tagged
    #
    def self.get_feeds_tagged(tab = "new", filter_tags = nil, options = {:limit => 20})
        case tab
        when "new"
            order = "feeds.created_at DESC"
        when "alpha"
            order = "lower(feeds.title) ASC"
        else # popular
            order = "subscribers DESC, lower(feeds.title) ASC"
        end

        sql = "SELECT feeds.id, feeds.title, feeds.description, feeds.display_uri, feeds.uri "
        sql << "FROM feeds "
        sql << "INNER JOIN entries ON entries.feed_id = feeds.id "
        sql << "INNER JOIN entries_tags"
        sql << "INNER JOIN tags ON feeds_users_tags.tag_id = tags.id "
        sql << "WHERE " + make_and_sql_where("tags.name", filter_tags) if filter_tags
        sql << "GROUP BY feeds.id, feeds.title, feeds.description, feeds.display_uri, feeds.uri "
        sql << "ORDER BY " + order
        add_limit!(sql,options)

        find_by_sql(sql)
    end

    ##########################################################
    # gets tags that have been been applied to entries
    def self.get_feed_tags(options = {})
        sql = "SELECT name, count FROM ("
        sql << "SELECT tags.name, COUNT(tags.id) as count "
        sql << "FROM tags "
        sql << "INNER JOIN entries_tags ON entries_tags.tag_id = tags.id "
        sql << "INNER JOIN entries ON entries.id = entries_tags.entry_id "
        sql << "GROUP BY tags.name "
        add_limit!(sql,options)
        sql << ") AS t ORDER BY LOWER(t.name)"
        Tag.find_by_sql(sql)
    end

    ##########################################################
    # Finds tags applied to entries in a feed, possibly narrowed
    # to the entries that have been tagged with specified tags
    #
    # Called by:
    #   feed_controller.show
    #
    def get_filter_tags(tags, options = {})

        sql = "SELECT tags.name, COUNT(tags.id) as count "
        sql << "FROM entries "
        sql << "INNER JOIN entries_tags ON entries_tags.entry_id = entries.id "
        sql << "INNER JOIN tags ON entries_tags.tag_id = tags.id "
        sql << "WHERE entries.feed_id = ? "
        if tags and tags.length > 0
            sql << "AND entries.id IN "
            sql << "("
            #The following code finds entries that share the same tags
            sql << "	SELECT et1.entry_id "
            sql << "	FROM entries_tags AS et1 "
            sql << "	INNER JOIN tags t1 ON t1.id = et1.tag_id "

            counter = 1
            tags.each do |tag|
                counter = counter + 1
                sql << "INNER JOIN entries_tags AS et" + counter.to_s + " ON et" + counter.to_s + ".entry_id = et1.entry_id "
                sql << "INNER JOIN tags t" + counter.to_s + " ON t" + counter.to_s + ".id = et" + counter.to_s + ".tag_id "
            end

            sql << "WHERE "

            connector = ""
            counter = 1
            tags.each do |tag|
                counter = counter + 1
                sql << connector + " t" + counter.to_s + ".name = '" + tag + "' "
                connector = " AND "
            end

            sql << ") "
        end
        sql << "GROUP BY tags.id, tags.name "
        sql << "ORDER BY count DESC "

        add_limit!(sql, options)

        result = Tag.find_by_sql([sql, self.id])
        count = result.inject({}) { |hsh, row| hsh[row['name']] = row['count'].to_i; hsh } unless options[:raw]
        count || result
    end
    
    protected

    ##########################################################
    # creates a feed from a uri
    def create_uri_feed(uri, aggregation, service)
        feed = Feed.find_or_create(uri, uri, '', '', service.id)
        feed.user = nil
        add_feed_to_aggregation(aggregation, feed)
    end
    
    ##########################################################
    # creates a feed for a service with a username and optional password
    def create_username_password_feed(user, login, password, service, aggregation)
        uri = service.uri_template.sub("{username}", login)
        title = login + "'s " + service.title
        feed = Feed.find_or_create(uri, title, login, password, service.id)
        feed.user = user
        feed.save
        add_feed_to_aggregation(aggregation, feed)        
    end

    
  end
end
